<?php abstract class Daemon implements iExtensionsRequired, iReconfigurable, iSignalHandler {
	protected static $Detached = FALSE;
	protected static $Sleep    = 30;
	protected static $Queue    = array ();
	protected static $Cyclers  = array ();
	protected static $SigMap   = array ();
	public static function __GetRequiredExtensions () {return (array ('pcntl', 'posix'));}
	public static function __Signals ($Sig = NULL) {
		if (NULL === $Sig) return (array (SIGHUP, SIGINT, SIGQUIT, SIGTERM));
		switch ($Sig) {
			case SIGINT: case SIGQUIT: case SIGTERM:
				Out::I (Msg::SigHandled, $Sig);
				self::Down ();
			break;
			case SIGHUP:
				Out::I (Msg::SigHandled, $Sig);
				self::Push2Q (array (__CLASS__, 'ParseConfig'));
			break;
		}
	}
	public static function Detach () {
		if (Options::NoFork ()) {
			Out::I (Msg::NotForkingOut);
			return (NULL);
		}
		Out::I (Msg::ForkingOut);
		switch (pcntl_fork ()) {
			default: exit (0);
			case -1: Out::W (Msg::ForkFailed); return (NULL);
			case 0:
				file_put_contents ($PidFile = Options::PidFile (), ($Pid = getmypid ())."\n") or Out::W (Msg::PidSaveError, $Pid, $PidFile);
				ini_set ('display_errors', 0);
				ini_set ('log_errors', 1);
				ini_set ('error_log', 'syslog');
				posix_setsid ();
				self::$Detached = TRUE;
				fclose (STDIN);
				fclose (STDOUT);
				fclose (STDERR);
		}
	}
	public static function ParseConfig () {
		if (is_readable ($Filename = Options::Config ())) {
			$Config = parse_ini_file ($Filename, TRUE);
			Out::D (Msg::ConfigDump, $Filename, print_r ($Config, TRUE));
			foreach ($Config as $Class => $ClassConfig) if (class_exists ($Class) and HasInterface ($Class, 'iReconfigurable')) {
				Out::I (Msg::Reconfigure, $Class);
				call_user_func (array ($Class, 'Reconfigure'), $ClassConfig);
			}
		} else Out::W (Msg::ReadFileFail, $Filename);
	}
	public static function Reconfigure (array $Config) {
		if (array_key_exists ('sleep', $Config)) {
			Out::I (Msg::ConfigGot, 'sleep', $Sleep = $Config ['sleep']);
			if ($Sleep >= 1) {
				Out::I (Msg::ConfigApplied, 'sleep', self::$Sleep = $Sleep);
			} else Out::W (Msg::BadSleep, $Sleep, self::$Sleep);
		}
		// try to change user and group
		if (array_key_exists ('user', $Config) and strlen ($UID = $Config ['user'])) {
			Out::I (Msg::ConfigGot, 'user', $UID);
			// determine numbers
			$UID = ($UID !== (string) (int) $UID) ? posix_getpwnam ($UID) : posix_getpwuid ((int) $UID);
			if (array_key_exists ('group', $Config) and strlen ($GID = $Config ['group'])) {
				Out::I (Msg::ConfigGot, 'group', $GID);
				$GID = ($GID !== (string) (int) $GID) ? posix_getgrnam ($GID) : posix_getgrgid ((int) $GID);
				$GID = $GID ['gid'];
			} else $GID = $UID ['gid'];
			$UID = $UID ['uid'];
			// changing if nessesary
			if ($GID !== ($CurGID = posix_getgid ())) {
				if (posix_setgid ($GID)) {
					Out::I (Msg::ChangedID, 'GID', $GID);
				} else Out::W (Msg::ChangeIDFailed, 'GID', $GID, $CurGID);
			} else Out::I (Msg::NotChangingID, 'GID', $GID);
			if ($UID !== ($CurUID = posix_getuid ())) {
				if (posix_setuid ($UID)) {
					Out::I (Msg::ChangedID, 'UID', $UID);
				} else Out::W (Msg::ChangeIDFailed, 'UID', $UID, $CurUID);
			} else Out::I (Msg::NotChangingID, 'UID', $UID);
		}
	}
	public static function Detached () {return (self::$Detached);}
	public static function Push2Q ($Callback, $Args = NULL) {
		if (! is_callable ($Callback)) return (FALSE);
		$Callback = array ('cb' => $Callback);
		$Callback ['args'] = (NULL === $Args) ? array () : $Args;
		is_array ($Callback ['args']) or $Callback ['args'] = array ($Callback ['args']);
		array_push (self::$Queue, $Callback);
	}
	public static function SigHandler ($Sig = NULL) {
		if (NULL === $Sig) {
			foreach (get_declared_classes () as $Class) if (HasInterface ($Class, 'iSignalHandler')) {
				foreach (call_user_func (array ($Class, '__Signals')) as $Sig) self::$SigMap [$Sig][] = $Class;
			}
			Out::I (Msg::SigInstall);
			foreach (array_keys (self::$SigMap) as $Sig) pcntl_signal ($Sig, array (__CLASS__, __FUNCTION__));
			return (TRUE);
		}
		Out::I (Msg::SigCaught, $Sig);
		if (array_key_exists ($Sig, self::$SigMap)) {
			foreach (self::$SigMap [$Sig] as $Class) {
				if (is_callable ($Callback = array ($Class, '__Signals'))) {
					Out::I (Msg::Callback, implode ('::', $Callback));
					call_user_func ($Callback, $Sig);
				} else Out::W (Msg::Uncallable, implode ('::', $Callback));
			}
		} else Out::W (Msg::SigUnknown, $Sig);
	}
	public static function Main () {
		Out::I (Msg::MainLoop);
		foreach (get_declared_classes () as $Class) if (HasInterface ($Class, 'iCycler')) array_push (self::$Cyclers, $Class);
		while (TRUE) {
			for ($i = 1; $i < self::$Sleep; $i += 1) sleep (1); // this quantisation is needed so that any signal handled will not break the whole sleep
			// run filters
			if (is_array ($Lines = DB::Fetch ())) foreach ($Lines as $Line) Filter::Examine ($Line);
			Filter::Commit ();
			// process queue
			foreach (self::$Queue as $Callback) call_user_func_array ($Callback ['cb'], $Callback ['args']);
			self::$Queue = array ();
			// process cyclers
			foreach (self::$Cyclers as $Cycler) call_user_func (array ($Cycler, 'Cycle'));
		}
	}
	public static function Down () {
		Out::I (Msg::ExitingClean);
		self::$Detached and @unlink (Options::PidFile ());
		exit (0);
	}
} ?>